﻿/*
	This file is part of Limpet.

    Limpet is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Limpet is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with Limpet.  If not, see http://www.gnu.org/licenses/.
	
	Copyright 2010 Dan Popick
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace Open.Web.Diagnostics.Limpet
{
    /// <summary>
    /// A simple stream which converts written data to base64-encoded ASCII 
    /// data on the underlying stream.
    /// </summary>
    public class Base64EncodingStream : Stream
    {
        // An array which maps acceptable base64 values (0-63) to ASCII characters
        static byte[] _intToChar;

        /// <summary>
        /// Static constructor, initializes the byte array
        /// </summary>
        static Base64EncodingStream()
        {
            _intToChar = System.Text.Encoding.ASCII.GetBytes("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
        }

        // the underlying stream, to which we will be writing the base64-encoded ASCII data
        Stream _baseStream;
        // the current byte offset of the output block (0 - 2)
        int _cbit = 0;
        // remaining bits to be included with the next output byte
        byte _remaining = 0;
        // the working 4-byte block
        byte[] _working = new byte[4];

        /// <summary>
        /// Constructor taking a base stream
        /// </summary>
        /// <param name="baseStream">The stream to which base64 data is written</param>
        public Base64EncodingStream(Stream baseStream)
        {
            _baseStream = baseStream;
        }

        #region ObMethods for Stream
        public override bool CanRead { get { return false; } }
        public override bool CanSeek { get { return false; } }
        public override bool CanWrite { get { return true; } }

        public override long Length
        {
            get { throw new InvalidOperationException(); }
        }

        public override long Position
        {
            get { throw new InvalidOperationException(); }
            set { throw new InvalidOperationException(); }
        }

        public override int Read(byte[] buffer, int offset, int count) { throw new InvalidOperationException(); }
        public override long Seek(long offset, SeekOrigin origin) { throw new InvalidOperationException(); }
        public override void SetLength(long value) { throw new InvalidOperationException(); }

        public override void Flush()
        {
            _working[3] = 61;
            _working[2] = 61;
            _working[_cbit] = _intToChar[_remaining];
            if (_cbit != 0)
                _baseStream.Write(_working, 0, 4);
            _baseStream.Flush();
        }
        #endregion

        public override void Write(byte[] buffer, int offset, int count)
        {
            for (int i = 0; i < count; ++i)
            {
                byte datum = buffer[offset + i];
                WriteByte(datum);
            }
        }

        public override void WriteByte(byte value)
        {
            int bitOffset = (_cbit * 2) + 2;
            _working[_cbit] = _intToChar[(value >> bitOffset) + _remaining];
            if (_cbit == 2)
            {
                _working[3] = _intToChar[value & 0x3f];
                _baseStream.Write(_working, 0, 4);
                _remaining = 0;
            }
            else
                _remaining = (byte)(value << (6 - bitOffset) & 0x3f);
            _cbit = (_cbit + 1) % 3;
        }
    }
}
